#include "preHeader.h"
#include "GuildLeagueMgr.h"
#include "InstCfgMgr.h"
#include "GameServer.h"
#include "Session/SocialServerSession.h"
#include "Session/MapServerMgr.h"

STATIC_ASSERT(GuildLeagueJoinRankBest % 4 == 0);

static const uint32 sBattleTable[2][2][2] = {
	{{0,2},{1,3},},
	{{0,1},{2,3},},
};

GuildLeagueMgr::GuildLeagueMgr()
: m_isFirstLeague(false)
, m_keepUpLeagueTimes(0)
, m_lastLockLeagueTime(0)
, m_leagueStartTime(0)
, m_leaguePrepareTime(0)
, m_leagueDurationTime(0)
, m_pBattleTable(NULL)
, m_instSeed(0)
{
}

GuildLeagueMgr::~GuildLeagueMgr()
{
}

WheelTimerMgr *GuildLeagueMgr::GetWheelTimerMgr()
{
	return &sGameServer.sWheelTimerMgr;
}

void GuildLeagueMgr::Init()
{
	TriggerPoint firstRoundTriggerPoint, secondRoundTriggerPoint;
	time_t firstRoundDuration, secondRoundDuration;
	auto unpacker = GetGameTimeUnpacker(GameTime::Type::GuildLeague);
	auto wdays = UnpackGameTime4Days(unpacker);
	auto lockTriggerPoint = UnpackGameTime4Trigger(unpacker, ByDailyTrigger);
	std::tie(firstRoundTriggerPoint,
		firstRoundDuration) = UnpackGameTime4Twinkler(unpacker, ByDailyTrigger);
	auto firstRoundPrepare = unpacker.Unpack<time_t>();
	std::tie(secondRoundTriggerPoint,
		secondRoundDuration) = UnpackGameTime4Twinkler(unpacker, ByDailyTrigger);
	auto secondRoundPrepare = unpacker.Unpack<time_t>();

	CreateTriggerX(ByDailyTrigger, lockTriggerPoint, [=, wdays = std::move(wdays)]() {
		if (IsInDameDays(wdays, ByDailyTrigger)) {
			LockGuildLeagueParticipant();
		}
	});
	CreateTwinklerX(ByDailyTrigger, firstRoundTriggerPoint, firstRoundDuration + 10, [=]() {
		if (m_lastLockLeagueTime > GET_DAY_UNIX_TIME) {
			m_leagueStartTime = CalcPreviousTriggerPointTime(ByDailyTrigger, firstRoundTriggerPoint);
			m_leaguePrepareTime = firstRoundPrepare, m_leagueDurationTime = firstRoundDuration;
			StartFirstRoundGuildLeague();
		}
	}, [=]() {
		if (m_lastLockLeagueTime > GET_DAY_UNIX_TIME) {
			StopFirstRoundGuildLeague();
		}
	});
	CreateTwinklerX(ByDailyTrigger, secondRoundTriggerPoint, secondRoundDuration + 10, [=]() {
		if (m_lastLockLeagueTime > GET_DAY_UNIX_TIME) {
			m_leagueStartTime = CalcPreviousTriggerPointTime(ByDailyTrigger, secondRoundTriggerPoint);
			m_leaguePrepareTime = secondRoundPrepare, m_leagueDurationTime = secondRoundDuration;
			StartSecondRoundGuildLeague();
		}
	}, [=]() {
		if (m_lastLockLeagueTime > GET_DAY_UNIX_TIME) {
			StopSecondRoundGuildLeague();
		}
	});
}

void GuildLeagueMgr::LoadData()
{
	LoadGuildLeagueStatus();
}

void GuildLeagueMgr::CleanInvalidGuilds(const std::unordered_set<uint32>& guildIds)
{
	for (auto& guildId : m_guildLeagueRanks) {
		if (guildIds.count(guildId) == 0) {
			guildId = 0;
		}
	}
}

GErrorCode GuildLeagueMgr::HandleJoinGuildLeague(Character* pChar)
{
	if (m_leagueStartTime == 0 ||
		m_leagueStartTime + m_leaguePrepareTime < GET_UNIX_TIME) {
		return ErrGuildLeagueNotJoinTime;
	}

	if (pChar->guildId == 0) {
		return ErrNotInGuild;
	}
	auto fightPos = GetGuildLeagueFightPos(pChar->guildId);
	if (fightPos == -1) {
		return ErrGuildLeagueNotQualification;
	}

	auto instPos = GetGuildLeagueInstancePos(fightPos);
	EnsureGuildLeagueInstance(instPos);
	auto& fightInstGuid = m_guildLeagueInsts[instPos];
	if (fightInstGuid.UID == INST_UID_INVALID) {
		return ErrGuildLeagueAlreadyFinished;
	}

	auto instOwner = GetGuidFromLowGuid(TYPE_GUILD, pChar->guildId);
	sMapServerMgr.RelocateCharacter(
		pChar, instOwner, fightInstGuid, vector3f1f_NULL);

	return CommonSuccess;
}

GErrorCode GuildLeagueMgr::OnFinishGuildLeague(
	InstGUID instGuid, uint32 winGuildId, time_t startTime)
{
	if (startTime != m_leagueStartTime) {
		return CommonNoWarning;
	}
	auto fightPos = GetGuildLeagueFightPos(winGuildId);
	if (fightPos == -1) {
		return CommonInternalError;
	}
	auto instPos = GetGuildLeagueInstancePos(fightPos);
	if (instPos == -1) {
		return CommonInternalError;
	}
	auto& leagueInstGuid = m_guildLeagueInsts[instPos];
	if (leagueInstGuid != instGuid) {
		return CommonInternalError;
	}

	leagueInstGuid.UID = INST_UID_INVALID;
	auto&& [guildId1, guildId2] = GetGuildLeagueMember(instPos);
	DBGASSERT(guildId1 == winGuildId || guildId2 == winGuildId);
	if (guildId1 != winGuildId && guildId2 == winGuildId) {
		std::swap(guildId1, guildId2);
	}

	return CommonSuccess;
}

std::pair<uint32, uint32> GuildLeagueMgr::GetGuildLeagueMember(
	InstGUID instGuid, time_t startTime)
{
	if (startTime != m_leagueStartTime) {
		return {0, 0};
	}
	auto itr = std::find(std::begin(m_guildLeagueInsts),
		std::end(m_guildLeagueInsts), instGuid);
	if (itr == std::end(m_guildLeagueInsts)) {
		return {0, 0};
	}
	auto instPos = std::distance(m_guildLeagueInsts, itr);
	return GetGuildLeagueMember(instPos);
}

void GuildLeagueMgr::EnsureGuildLeagueInstance(size_t instPos)
{
	auto& instGuid = m_guildLeagueInsts[instPos];
	if (instGuid != InstGUID_NULL) {
		return;
	}
	if (!IsGuildLeagueInstantiate(instPos)) {
		FastFinishGuildLeagueInstance(instPos);
		return;
	}
	m_instSeed += 1;
	instGuid = MakeInstGuid((u16)MapInfo::Type::GuildLeague,
		sGameConfig.GuildLeagueMapId, m_instSeed);
	NetBuffer args;
	args << (int64)m_leagueStartTime
		<< (u32)m_leaguePrepareTime << (u32)m_leagueDurationTime;
	auto&& [fightGroup, instIndex] = std::div((int)instPos, 2);
	for (auto fightIndex : (*m_pBattleTable)[instIndex]) {
		args << m_guildLeagueFights[fightGroup * 4 + fightIndex];
	}
	sMapServerMgr.StartInstance(
		instGuid, ObjGUID_NULL, 0, 0, args.CastBufferStringView());
}

void GuildLeagueMgr::FastFinishGuildLeagueInstance(size_t instPos)
{
	m_guildLeagueInsts[instPos].UID = INST_UID_INVALID;
	auto&& [guildId1, guildId2] = GetGuildLeagueMember(instPos);
	if (guildId1 == 0) {
		std::swap(guildId1, guildId2);
	}
}

void GuildLeagueMgr::ResetAllGuildLeagueInstances()
{
	std::fill_n(m_guildLeagueInsts,
		ARRAY_SIZE(m_guildLeagueInsts), InstGUID_NULL);
}

bool GuildLeagueMgr::IsGuildLeagueInstantiate(size_t instPos) const
{
	auto&& [fightGroup, instIndex] = std::div((int)instPos, 2);
	for (auto fightIndex : (*m_pBattleTable)[instIndex]) {
		if (m_guildLeagueFights[fightGroup * 4 + fightIndex] == 0) {
			return false;
		}
	}
	return true;
}

size_t GuildLeagueMgr::GetGuildLeagueInstancePos(size_t fightPos) const
{
	auto&& [fightGroup, fightIndex] = std::div((int)fightPos, 4);
	for (size_t i = 0; i < ARRAY_SIZE(*m_pBattleTable); ++i) {
		if (IS_ARRAY_CONTAIN_VALUE((*m_pBattleTable)[i], fightIndex)) {
			return fightGroup * 2 + i;
		}
	}
	return -1;
}

size_t GuildLeagueMgr::GetGuildLeagueFightPos(uint32 guildId) const
{
	auto itr = std::find(
		m_guildLeagueFights.begin(), m_guildLeagueFights.end(), guildId);
	if (itr != m_guildLeagueFights.end()) {
		return std::distance(m_guildLeagueFights.begin(), itr);
	}
	return -1;
}

std::pair<uint32&, uint32&> GuildLeagueMgr::GetGuildLeagueMember(size_t instPos)
{
	auto&& [fightGroup, instIndex] = std::div((int)instPos, 2);
	auto& fightIndexs = (*m_pBattleTable)[instIndex];
	return {m_guildLeagueFights[fightGroup * 4 + fightIndexs[0]],
		m_guildLeagueFights[fightGroup * 4 + fightIndexs[1]]};
}

void GuildLeagueMgr::LockGuildLeagueParticipant()
{
	NetPacket rpcReqPck(CGX_RANK_GET_GUILD_ID_LIST);
	rpcReqPck << (u8)SocialRPCReply::GameServer << (u8)RANK_SUBTYPE::GUILD_FIGHT_VALUE
		<< (u32)0 << u32(GuildLeagueJoinRankBest + 4);
	sSocialServerSession.RPCInvoke(rpcReqPck, [=](INetStream& pck, int32 err, bool) {
		if (err != DBPErrorNone) {
			WLOG("Pull guild rank failed, %d.", err);
			return;
		}
		m_lastLockLeagueTime = GET_UNIX_TIME;
		m_guildLeagueFights = m_guildLeagueRanks;
		if (!m_isFirstLeague && m_guildLeagueFights.size() < GuildLeagueJoinRankBest) {
			m_guildLeagueFights.resize(GuildLeagueJoinRankBest);
		}
		while (!pck.IsReadableEmpty()) {
			auto guildId = pck.Read<uint32>();
			if (!IS_VECTOR_CONTAIN_VALUE(m_guildLeagueFights, guildId)) {
				m_guildLeagueFights.push_back(guildId);
				if (m_guildLeagueFights.size() >= GuildLeagueJoinRankBest + 4) {
					break;
				}
			}
		}
	}, &sGameServer, DEF_S2S_RPC_TIMEOUT);
}

void GuildLeagueMgr::StartFirstRoundGuildLeague()
{
	m_guildLeagueFights.resize(
		DivWithCeil(m_guildLeagueFights.size(), size_t(4)));
	for (size_t i = 0, n = m_guildLeagueFights.size(); i < n; i += 4) {
		RandomShuffle(m_guildLeagueFights.begin() + i,
			m_guildLeagueFights.begin() + i + 4);
	}
	ResetAllGuildLeagueInstances();
	m_pBattleTable = &sBattleTable[0];
}

void GuildLeagueMgr::StopFirstRoundGuildLeague()
{
	m_leagueStartTime = 0;
}

void GuildLeagueMgr::StartSecondRoundGuildLeague()
{
	ResetAllGuildLeagueInstances();
	m_pBattleTable = &sBattleTable[1];
}

void GuildLeagueMgr::StopSecondRoundGuildLeague()
{
	m_leagueStartTime = 0;
	if (!m_guildLeagueRanks.empty() &&
		m_guildLeagueRanks[0] == m_guildLeagueFights[0]) {
		m_keepUpLeagueTimes += 1;
	} else {
		m_keepUpLeagueTimes = 1;
	}
	for (size_t i = 1, n = m_guildLeagueFights.size(); i < n; i += 4) {
		if (m_guildLeagueFights[i*4] != 0) {
			std::swap(m_guildLeagueFights[i*4-1], m_guildLeagueFights[i*4]);
		}
	}
	m_guildLeagueFights.resize(GuildLeagueJoinRankBest);
	m_guildLeagueRanks = std::move(m_guildLeagueFights);
	SaveGuildLeagueStatus();
}

void GuildLeagueMgr::SaveGuildLeagueStatus()
{
	TextPacker packer;
	packer << m_keepUpLeagueTimes;
	for (auto guildId : m_guildLeagueRanks) {
		packer << guildId;
	}
	sInstCfgMgr.SaveInstCfgVal(InstCfg_GuildLeagueStatus, packer.str());
}

void GuildLeagueMgr::LoadGuildLeagueStatus()
{
	auto& cfgVal = sInstCfgMgr.GetInstCfgVal(InstCfg_GuildLeagueStatus);
	if (cfgVal.empty()) {
		m_isFirstLeague = true;
		return;
	}
	TextUnpacker unpacker(cfgVal.c_str());
	unpacker >> m_keepUpLeagueTimes;
	while (!unpacker.IsEmpty()) {
		m_guildLeagueRanks.push_back(unpacker.Unpack<uint32>());
	}
}
